using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Office.Interop.Excel;
using Microsoft.Office.Core;
using Action = System.Action;
using MsoDocProperties = Microsoft.Office.Core.MsoDocProperties;


namespace ExcelRefresh.Com
{
    public abstract class ComHelpers
    {
        /// <summary>
        /// Executes an Action within a code block that guarantees the release of an underlying COM object.
        /// </summary>
        public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
        {
            UsingCOM(reference, doThis, null);
        }

        /// <summary>
        /// Executes an Action within a code block that guarantees the release of an underlying COM object.
        /// </summary>
        public static void UsingCOM<T>(T reference, Action<T> doThis, Action<T> thenFinally) where T : class
        {
            if (reference == null) return;
            try
            {
                doThis(reference);
            }
            finally
            {
                if (thenFinally == null)
                    while (Marshal.ReleaseComObject(reference) > 0) { }
                else
                    try
                    {
                        thenFinally(reference);
                    }
                    finally
                    {
                        while (Marshal.ReleaseComObject(reference) > 0) { }
                    }

            }
        }


        public static void UsingAutomationApp<T>(
                      T app,
                      Func<T, int> fnGethWnd,
                      Action<T, Process> doThis) where T : class
        {
            using (new OleMessageFilter())
            {
                var process = GetProcessFromHWnd(fnGethWnd(app));
                try
                {
                    doThis(app, process);
                }
                finally
                {
                    while (Marshal.ReleaseComObject(app) > 0) { }
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    GC.GetTotalMemory(true);

                    if (!process.HasExited)
                        process.Kill();
                }
            }
        }


        [DllImport("user32.dll")]
        static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

        public static Process GetProcessFromHWnd(int hWnd)
        {
            int processId;
            GetWindowThreadProcessId(hWnd, out processId);
            return Process.GetProcessById(processId);
        }

        public static void UsingExcel(Action<Application> doThis)
        {
            UsingAutomationApp(
                new Application { Visible = false, DisplayAlerts = false },
                app => app.Hwnd,
                (app, process) =>
                {
                    app.MultiThreadedCalculation.Enabled = true;
                    app.DisplayAlerts = false;
                    try
                    {
                        doThis(app);
                    }
                    finally
                    {
                        app.Quit();
                    }
                });
        }

        public static void UsingWorkbook(string workbookFilePath, params Action<Workbook>[] doThisWork)
        {
            UsingExcel(excelApp =>
                UsingCOM(
                    excelApp.Workbooks.Open(workbookFilePath,
                                            XlUpdateLinks.xlUpdateLinksNever   /* [don't] Update links */,
                                            false                              /* Readonly OK? */,
                                            Type.Missing                       /* Format */,
                                            Type.Missing                       /* Password */,
                                            Type.Missing                       /* Write Res Password */,
                                            true                               /* Ignore Readonly Recommended */,
                                            Type.Missing                       /* Origin (codepage)*/,
                                            Type.Missing                       /* Delimiter */,
                                            Type.Missing                       /* Enable editable addins */,
                                            false                              /* Open in notify mode if locked by other user? */
                                            ),
                    workBook =>
                    {
                        for (var i = 0; i < doThisWork.Length; i++) doThisWork[i](workBook);
                    },
                    workBook => workBook.Close(false)
                )
            );
        }
    }
}